package cn.thoughtworks.school.programCenter.services;

import cn.thoughtworks.school.programCenter.entities.*;
import cn.thoughtworks.school.programCenter.entities.commands.CreateUserAssignmentDurationCommand;
import cn.thoughtworks.school.programCenter.entities.vo.ProgramAssignmentsVo;
import cn.thoughtworks.school.programCenter.exceptions.BusinessException;
import cn.thoughtworks.school.programCenter.repositories.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class AssignmentService {
    @Autowired
    private ReviewQuizRepository reviewQuizRepository;
    @Autowired
    private QuizSuggestionRepository quizSuggestionRepository;
    @Autowired
    private UserCenterService userCenterService;
    @Autowired
    private AssignmentRepository assignmentRepository;
    @Autowired
    private UserAssignmentDurationRepository userAssignmentDurationRepository;
    @Autowired
    private ReviewQuizService reviewQuizService;
    @Autowired
    private QuizCenterService quizCenterService;
    @Autowired
    private ProgramService programService;
    @Autowired
    private TaskRepository taskRepository;

    public String getAssignmentStatusOld(Assignment assignment, List<ReviewQuiz> submitAssignments) {
        List<AssignmentQuiz> selectedQuizzes = assignment.getSelectedQuizzes();
        if (isUnfinished(assignment.getType(), selectedQuizzes, submitAssignments)) {
            return "unfinished";
        }
        if (isReviewing(assignment.getType(), submitAssignments)) {
            return "reviewing";
        }
        if (isFinished(assignment.getType(), submitAssignments)) {
            return "finished";
        }
        return "excellent";
    }

    public String getAssignmentStatus(Assignment assignment, boolean hasAssignmentQuizTag, List<ReviewQuiz> submitAssignments) {
        List<AssignmentQuiz> selectedQuizzes = assignment.getSelectedQuizzes();
        if (isUnfinished(assignment.getType(), selectedQuizzes, submitAssignments)) {
            return "unfinished";
        }
        if (isReviewing(assignment.getType(), submitAssignments)) {
            return "reviewing";
        }
        if (hasAssignmentQuizTag) {
            return "assessed";
        }
        return "unAssessed";
    }

    private boolean isFinished(String type, List<ReviewQuiz> submitAssignments) {
        if (Objects.equals(type, "BASIC_QUIZ") || Objects.equals(type, "SURVEY_QUIZ")) {
            return submitAssignments.get(0).getStatus().equals("已完成");
        }
        return submitAssignments.stream().anyMatch(item -> Objects.equals(item.getStatus(), "已完成"));
    }

    private boolean isReviewing(String type, List<ReviewQuiz> submitAssignments) {
        if (Objects.equals(type, "BASIC_QUIZ") || Objects.equals(type, "SURVEY_QUIZ")) {
            return submitAssignments.get(0).getStatus().equals("已提交");
        }

        return submitAssignments.stream().anyMatch(item -> Objects.equals(item.getStatus(), "已提交"));
    }

    private boolean isUnfinished(String type, List<AssignmentQuiz> selectedQuizzes, List<ReviewQuiz> submitAssignments) {
        if (Objects.equals(type, "BASIC_QUIZ") || Objects.equals(type, "SURVEY_QUIZ")) {
            return submitAssignments.size() != 1;
        }
        return selectedQuizzes.size() != submitAssignments.size() || selectedQuizzes.size() == 0;

    }

    public List<QuizSuggestion> getQuizSuggestions(Long studentId, Long assignmentId, Long quizId) {
        List<QuizSuggestion> suggestions = quizSuggestionRepository.findByAssignmentIdAndQuizIdAndStudentId(assignmentId, quizId, studentId);
        List<Map> users = getSuggestionsUsers(suggestions);
        wrapUsers(suggestions, users);

        return filter(studentId, suggestions);
    }

    private List<QuizSuggestion> filter(Long studentId, List<QuizSuggestion> suggestions) {
        List<QuizSuggestion> result = new ArrayList<>();
        suggestions.forEach(quizSuggestion -> {
            if (isTopParentSuggestion(studentId, quizSuggestion)) {
                setChildQuizSuggestions(suggestions, quizSuggestion);
                result.add(quizSuggestion);
            }
        });

        return result;
    }

    private boolean isTopParentSuggestion(Long studentId, QuizSuggestion quizSuggestion) {
        return (Objects.isNull(quizSuggestion.getParentId()) || quizSuggestion.getParentId() == 0) &&
            quizSuggestion.getToUserId().equals(studentId);
    }

    private void wrapUsers(List<QuizSuggestion> suggestions, List<Map> users) {
        suggestions.forEach(suggestion -> {
            suggestion.setFromUser(findSuggestionUser(users, suggestion.getFromUserId()));
            suggestion.setToUser(findSuggestionUser(users, suggestion.getToUserId()));
        });
    }

    private void setChildQuizSuggestions(List<QuizSuggestion> foundSuggestions, QuizSuggestion quizSuggestion) {
        List<QuizSuggestion> childs = foundSuggestions.stream().filter(child ->
            Objects.equals(child.getParentId(), quizSuggestion.getId())).collect(Collectors.toList());
        quizSuggestion.setChildSuggestions(childs);
    }

    private User findSuggestionUser(List<Map> users, Long fromUserId) {
        Map foundUser = users.stream().filter(user -> Objects.equals(Long.parseLong(user.get("id").toString()), fromUserId))
            .findFirst().orElse(new HashMap());
        Object userName = Objects.isNull(foundUser.get("name")) ? foundUser.get("username") : foundUser.get("name");
        Long id = Long.parseLong(foundUser.get("id").toString());

        return new User(Objects.isNull(userName) ? "" : userName.toString(), id);
    }

    private List<Map> getSuggestionsUsers(List<QuizSuggestion> suggestions) {
        List<Long> userIdsArr = new ArrayList<>();
        suggestions.forEach(suggestion -> {
            userIdsArr.add(suggestion.getFromUserId());
            userIdsArr.add(suggestion.getToUserId());
        });

        String userIds = Arrays.toString(userIdsArr.stream().distinct().collect(Collectors.toList()).toArray())
            .replace("[", "").replace("]", "").replace(" ", "");
        return userCenterService.getUsersByIds(userIds);
    }

    public List<Assignment> findByProgramId(Long programId) {
        return assignmentRepository.findByProgramId(programId);
    }

    public boolean isStart(Long assignmentId, Long studentId) {
        return userAssignmentDurationRepository.findByAssignmentIdAndUserId(assignmentId, studentId)
            .isPresent();
    }

    public void startAssignment(CreateUserAssignmentDurationCommand userAssignmentDuration) {
        Optional<UserAssignmentDuration> found = userAssignmentDurationRepository.findByAssignmentIdAndUserId(userAssignmentDuration.getAssignmentId(), userAssignmentDuration.getUserId());
        if (!found.isPresent()) {
            userAssignmentDurationRepository.save(userAssignmentDuration.build());
        }
    }

    public void finishAssignment(Long assignmentId, Long studentId) throws BusinessException {
        UserAssignmentDuration userAssignmentDuration = userAssignmentDurationRepository.findByAssignmentIdAndUserId(assignmentId, studentId)
            .orElseThrow(() -> new BusinessException("Unknown error"));

        userAssignmentDuration.finish();
        userAssignmentDurationRepository.save(userAssignmentDuration);
    }

    public void submitSurveyAssignmentAnswer(Long assignmentId, List<Map> submitAnswers, Long studentId) throws BusinessException {
        if (reviewQuizService.isSubmitted(assignmentId, studentId)) {
            throw new BusinessException("Please do not resubmit!");
        }

        quizCenterService.submitSurveyQuizAnswer(assignmentId, submitAnswers, studentId);

        Assignment assignment = assignmentRepository.findById(assignmentId)
            .orElseThrow(() -> new BusinessException(String.format("Unknown assignment with id: %s", assignmentId)));

        ReviewQuiz reviewQuiz = new ReviewQuiz(studentId, assignmentId, assignment.getTaskId(), 0, "已完成", new Date());
        reviewQuizRepository.save(reviewQuiz);
    }

    public UserAssignmentDuration getUserAssignmentDuration(Long id, Long taskId, Long assignmentId) {
        return userAssignmentDurationRepository.findByTaskIdAndAssignmentIdAndUserId(taskId, assignmentId, id)
            .orElse(new UserAssignmentDuration());
    }

    public List<ProgramAssignmentsVo> getCurrentOrgProgramsBasicQuizzes(Long userId) {
        Long organizationId = userCenterService.getUserCurrentOrganizationId(userId);
        List<Program> programs = programService.findByOrganizationId(organizationId);
        List<Task> tasks = getTasksByProgramIds(programs);

        return ProgramAssignmentsVo.build(programs, tasks);
    }

    private List<Task> getTasksByProgramIds(List<Program> programs) {
        List<Long> ids = programs.stream().map(Program::getId).collect(Collectors.toList());
        return taskRepository.findByProgramIdIn(ids);
    }

    public void calculateBasicQuizAssignmentScore(Long taskId, Long assignmentId) {
        List<ReviewQuiz> reviewQuizzes = reviewQuizService.findByTaskIdAndAssignmentId(taskId, assignmentId);
        Map<Long, List<Map>> submissions = quizCenterService.calculateBasicQuizAssignmentScore(assignmentId);
        reviewQuizzes.forEach(reviewQuiz -> {
            List<Map> quizSubmissions = submissions.get(reviewQuiz.getStudentId());
            if (Objects.nonNull(quizSubmissions)) {
                reviewQuiz.updateScore(calculateScore(quizSubmissions));
            }
        });
        reviewQuizRepository.saveAll(reviewQuizzes);
    }

    private int calculateScore(List<Map> quizSubmissions) {
        int correctCount = quizSubmissions.stream().filter(quizSubmissionStatus -> quizSubmissionStatus.get("correct").equals(true)).toArray().length;
        return (int) Math.rint((correctCount / (double) quizSubmissions.size()) * 100);

    }

    public List getAssignmentsByIds(List<Long> idList) {
        List<Assignment> assignments = assignmentRepository.getAllByIdIn(idList);
        assignments.stream().map(Assignment::getTitle).collect(Collectors.toList());
        return assignments;
    }
}
